﻿/*
Copyright © 2011 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System;
using System.Collections.Generic;
using System.IO;

namespace Simple.Storage.Serializer
{
    /// <summary>
    /// Implements a serializer for <see cref="ICollection{T}"/> instances.
    /// </summary>
    /// <typeparam name="T">
    /// The type of elements in the serialized collections.
    /// </typeparam>
    public class TypedCollectionSerializer<T> : AbstractSerializer
    {
        /// <summary>
        /// The serializer used to serialize the <typeparamref name="T"/> elements.
        /// </summary>
        private readonly ISerializer elementSerializer;

        /// <summary>
        /// Initializes a new instance of the TypedCollectionSerializer class.
        /// </summary>
        /// <param name="elementSerializer">
        /// The serializer used to serialize the <typeparamref name="T"/> elements.
        /// </param>
        public TypedCollectionSerializer(ISerializer elementSerializer)
        {
            if (elementSerializer == null)
            {
                throw new ArgumentNullException("elementSerializer");
            }
            else if (!elementSerializer.CanSerialize(typeof(T)))
            {
                throw new ArgumentException(
                    Messages.TypedCollectionSerializerArgument,
                    "elementSerializer");
            }

            this.elementSerializer = elementSerializer;
        }

        /// <summary>
        /// Implements the <see cref="ISerializer.CanSerialize"/> logic.
        /// </summary>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <returns>
        /// <c>true</c> if the serializer can work with instances of <paramref name="type"/>;
        /// <c>false</c> otherwise.
        /// </returns>
        protected override bool InternalCanSerialize(Type type)
        {
            return typeof(ICollection<T>).IsAssignableFrom(type);
        }

        /// <summary>
        /// Implements the <see cref="ISerializer.CanWorkInPlace"/> logic.
        /// </summary>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <returns>
        /// <c>true</c> if the serializer can work with initialized instances of
        /// <paramref name="type"/>; <c>false</c> otherwise.
        /// </returns>
        protected override bool InternalCanWorkInPlace(Type type)
        {
            return this.InternalCanSerialize(type);
        }

        /// <summary>
        /// Implements the serialization logic for <see cref="ICollection{T}"/>.
        /// </summary>
        /// <param name="writer">
        /// The writer - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="value">
        /// The value to serialize - guaranty to be not <c>null</c> and associated with
        /// the current serializer.
        /// </param>
        protected override void InternalSerialize(BinaryWriter writer, Type type, object value)
        {
            writer.Write(value != null);
            if (value != null)
            {
                ICollection<T> collection = value as ICollection<T>;
                writer.Write(collection.Count);

                foreach (T element in collection)
                {
                    this.elementSerializer.Serialize(writer, typeof(T), element);
                }
            }
        }

        /// <summary>
        /// Implements the deserialization logic.
        /// </summary>
        /// <param name="reader">
        /// The reader - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <returns>The deserialized value.</returns>
        protected override object InternalDeserialize(BinaryReader reader, Type type)
        {
            if (!reader.ReadBoolean())
            {
                return null;
            }

            int length = reader.ReadInt32();
            ICollection<T> collection;
            if (type.IsInterface)
            {
                collection = new List<T>(length);
            }
            else
            {
                collection = Activator.CreateInstance(type) as ICollection<T>;
            }

            for (int i = 0; i < length; i++)
            {
                collection.Add((T)this.elementSerializer.Deserialize(reader, typeof(T)));
            }

            return collection;
        }

        /// <summary>
        /// Implements the in-place deserialization logic.
        /// </summary>
        /// <param name="reader">
        /// The reader - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="value">
        /// The container for the deserialized object - guaranty to be not <c>null</c>.
        /// </param>
        protected override void InternalDeserialize(BinaryReader reader, Type type, object value)
        {
            if (!this.elementSerializer.CanSerialize(type))
            {
                // TODO: Raise an exception
            }

            ICollection<T> collection = (ICollection<T>)value;
            collection.Clear();

            if (!reader.ReadBoolean())
            {
                return;
            }

            int length = reader.ReadInt32();
            for (int i = 0; i < length; i++)
            {
                collection.Add((T)this.elementSerializer.Deserialize(reader, typeof(T)));
            }
        }
    }
}
